/*
 * $Id: customdata_file.c 35247 2011-02-27 20:40:57Z jesterking $
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/blenkernel/intern/customdata_file.c
 *  \ingroup bke
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "MEM_guardedalloc.h"

#include "BLI_fileops.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"

#include "BKE_customdata_file.h"
#include "BKE_global.h"


/************************* File Format Definitions ***************************/

#define CDF_ENDIAN_LITTLE	0
#define CDF_ENDIAN_BIG		1

#define CDF_DATA_FLOAT	0

typedef struct CDataFileHeader {
	char ID[4];					/* "BCDF" */
	char endian;				/* little, big */
	char version;				/* non-compatible versions */
	char subversion;			/* compatible sub versions */
	char pad;					/* padding */

	int structbytes;			/* size of this struct in bytes */
	int type;					/* image, mesh */
	int totlayer;				/* number of layers in the file */
} CDataFileHeader;

typedef struct CDataFileImageHeader {
	int structbytes;			/* size of this struct in bytes */
	int width;					/* image width */
	int height;					/* image height */
	int tile_size;				/* tile size (required power of 2) */
} CDataFileImageHeader;

typedef struct CDataFileMeshHeader {
	int structbytes;			/* size of this struct in bytes */
} CDataFileMeshHeader;

struct CDataFileLayer {
	int structbytes;				/* size of this struct in bytes */
	int datatype;					/* only float for now */
	uint64_t datasize;				/* size of data in layer */
	int type;						/* layer type */
	char name[CDF_LAYER_NAME_MAX];	/* layer name */
};

/**************************** Other Definitions ******************************/

#define CDF_VERSION			0
#define CDF_SUBVERSION		0
#define CDF_TILE_SIZE		64

struct CDataFile {
	int type;

	CDataFileHeader header;
	union {
		CDataFileImageHeader image;
		CDataFileMeshHeader mesh;
	} btype;

	CDataFileLayer *layer;
	int totlayer;

	FILE *readf;
	FILE *writef;
	int switchendian;
	size_t dataoffset;
};

/********************************* Create/Free *******************************/

static int cdf_endian(void)
{
	if(ENDIAN_ORDER == L_ENDIAN)
		return CDF_ENDIAN_LITTLE;
	else
		return CDF_ENDIAN_BIG;
}

/*static int cdf_data_type_size(int datatype)
{
	if(datatype == CDF_DATA_FLOAT)
		return sizeof(float);
	
	return 0;
}*/

CDataFile *cdf_create(int type)
{
	CDataFile *cdf= MEM_callocN(sizeof(CDataFile), "CDataFile");

	cdf->type= type;

	return cdf;
}

void cdf_free(CDataFile *cdf)
{
	cdf_read_close(cdf);
	cdf_write_close(cdf);

	if(cdf->layer)
		MEM_freeN(cdf->layer);

	MEM_freeN(cdf);
}

/********************************* Read/Write ********************************/

static int cdf_read_header(CDataFile *cdf)
{
	CDataFileHeader *header;
	CDataFileImageHeader *image;
	CDataFileMeshHeader *mesh;
	CDataFileLayer *layer;
	FILE *f= cdf->readf;
	size_t offset = 0;
	int a;

	header= &cdf->header;

	if(!fread(header, sizeof(CDataFileHeader), 1, cdf->readf))
		return 0;
	
	if(memcmp(header->ID, "BCDF", sizeof(header->ID)) != 0)
		return 0;
	if(header->version > CDF_VERSION)
		return 0;

	cdf->switchendian= header->endian != cdf_endian();
	header->endian= cdf_endian();

	if(cdf->switchendian) {
		SWITCH_INT(header->type);
		SWITCH_INT(header->totlayer);
		SWITCH_INT(header->structbytes);
	}

	if(!ELEM(header->type, CDF_TYPE_IMAGE, CDF_TYPE_MESH))
		return 0;

	offset += header->structbytes;
	header->structbytes= sizeof(CDataFileHeader);

	if(fseek(f, offset, SEEK_SET) != 0)
		return 0;
	
	if(header->type == CDF_TYPE_IMAGE) {
		image= &cdf->btype.image;
		if(!fread(image, sizeof(CDataFileImageHeader), 1, f))
			return 0;

		if(cdf->switchendian) {
			SWITCH_INT(image->width);
			SWITCH_INT(image->height);
			SWITCH_INT(image->tile_size);
			SWITCH_INT(image->structbytes);
		}

		offset += image->structbytes;
		image->structbytes= sizeof(CDataFileImageHeader);
	}
	else if(header->type == CDF_TYPE_MESH) {
		mesh= &cdf->btype.mesh;
		if(!fread(mesh, sizeof(CDataFileMeshHeader), 1, f))
			return 0;

		if(cdf->switchendian)
			SWITCH_INT(mesh->structbytes);

		offset += mesh->structbytes;
		mesh->structbytes= sizeof(CDataFileMeshHeader);
	}

	if(fseek(f, offset, SEEK_SET) != 0)
		return 0;

	cdf->layer= MEM_callocN(sizeof(CDataFileLayer)*header->totlayer, "CDataFileLayer");
	cdf->totlayer= header->totlayer;

	for(a=0; a<header->totlayer; a++) {
		layer= &cdf->layer[a];

		if(!fread(layer, sizeof(CDataFileLayer), 1, f))
			return 0;

		if(cdf->switchendian) {
			SWITCH_INT(layer->type);
			SWITCH_INT(layer->datatype);
			SWITCH_INT64(layer->datasize);
			SWITCH_INT(layer->structbytes);
		}

		if(layer->datatype != CDF_DATA_FLOAT)
			return 0;

		offset += layer->structbytes;
		layer->structbytes= sizeof(CDataFileLayer);

		if(fseek(f, offset, SEEK_SET) != 0)
			return 0;
	}

	cdf->dataoffset= offset;

	return 1;
}

static int cdf_write_header(CDataFile *cdf)
{
	CDataFileHeader *header;
	CDataFileImageHeader *image;
	CDataFileMeshHeader *mesh;
	CDataFileLayer *layer;
	FILE *f= cdf->writef;
	int a;

	header= &cdf->header;

	if(!fwrite(header, sizeof(CDataFileHeader), 1, f))
		return 0;
	
	if(header->type == CDF_TYPE_IMAGE) {
		image= &cdf->btype.image;
		if(!fwrite(image, sizeof(CDataFileImageHeader), 1, f))
			return 0;
	}
	else if(header->type == CDF_TYPE_MESH) {
		mesh= &cdf->btype.mesh;
		if(!fwrite(mesh, sizeof(CDataFileMeshHeader), 1, f))
			return 0;
	}

	for(a=0; a<header->totlayer; a++) {
		layer= &cdf->layer[a];

		if(!fwrite(layer, sizeof(CDataFileLayer), 1, f))
			return 0;
	}

	return 1;
}

int cdf_read_open(CDataFile *cdf, char *filename)
{
	FILE *f;

	f= fopen(filename, "rb");
	if(!f)
		return 0;
	
	cdf->readf= f;

	if(!cdf_read_header(cdf)) {
		cdf_read_close(cdf);
		return 0;
	}

	if(cdf->header.type != cdf->type) {
		cdf_read_close(cdf);
		return 0;
	}

	return 1;
}

int cdf_read_layer(CDataFile *cdf, CDataFileLayer *blay)
{
	size_t offset;
	int a;

	/* seek to right location in file */
	offset= cdf->dataoffset;
	for(a=0; a<cdf->totlayer; a++) {
		if(&cdf->layer[a] == blay)
			break;
		else
			offset += cdf->layer[a].datasize;
	}

	return (fseek(cdf->readf, offset, SEEK_SET) == 0);
}

int cdf_read_data(CDataFile *cdf, unsigned int size, void *data)
{
	float *fdata;
	unsigned int a;

	/* read data */
	if(!fread(data, size, 1, cdf->readf))
		return 0;

	/* switch endian if necessary */
	if(cdf->switchendian) {
		fdata= data;

		for(a=0; a<size/sizeof(float); a++)
			SWITCH_INT(fdata[a])
	}

	return 1;
}

void cdf_read_close(CDataFile *cdf)
{
	if(cdf->readf) {
		fclose(cdf->readf);
		cdf->readf= NULL;
	}
}

int cdf_write_open(CDataFile *cdf, char *filename)
{
	CDataFileHeader *header;
	CDataFileImageHeader *image;
	CDataFileMeshHeader *mesh;
	FILE *f;

	f= fopen(filename, "wb");
	if(!f)
		return 0;
	
	cdf->writef= f;

	/* fill header */
	header= &cdf->header;
	/* strcpy(, "BCDF"); // terminator out of range */
	header->ID[0]= 'B'; header->ID[1]= 'C'; header->ID[2]= 'D'; header->ID[3]= 'F';
	header->endian= cdf_endian();
	header->version= CDF_VERSION;
	header->subversion= CDF_SUBVERSION;

	header->structbytes= sizeof(CDataFileHeader);
	header->type= cdf->type;
	header->totlayer= cdf->totlayer;

	if(cdf->type == CDF_TYPE_IMAGE) {
		/* fill image header */
		image= &cdf->btype.image;
		image->structbytes= sizeof(CDataFileImageHeader);
		image->tile_size= CDF_TILE_SIZE;
	}
	else if(cdf->type == CDF_TYPE_MESH) {
		/* fill mesh header */
		mesh= &cdf->btype.mesh;
		mesh->structbytes= sizeof(CDataFileMeshHeader);
	}

	cdf_write_header(cdf);

	return 1;
}

int cdf_write_layer(CDataFile *UNUSED(cdf), CDataFileLayer *UNUSED(blay))
{
	return 1;
}

int cdf_write_data(CDataFile *cdf, unsigned int size, void *data)
{
	/* write data */
	if(!fwrite(data, size, 1, cdf->writef))
		return 0;

	return 1;
}

void cdf_write_close(CDataFile *cdf)
{
	if(cdf->writef) {
		fclose(cdf->writef);
		cdf->writef= NULL;
	}
}

void cdf_remove(char *filename)
{
	BLI_delete(filename, 0, 0);
}

/********************************** Layers ***********************************/

CDataFileLayer *cdf_layer_find(CDataFile *cdf, int type, char *name)
{
	CDataFileLayer *layer;
	int a;

	for(a=0; a<cdf->totlayer; a++) {
		layer= &cdf->layer[a];

		if(layer->type == type && strcmp(layer->name, name) == 0)
			return layer;
	}
	
	return NULL;
}

CDataFileLayer *cdf_layer_add(CDataFile *cdf, int type, char *name, size_t datasize)
{
	CDataFileLayer *newlayer, *layer;

	/* expand array */
	newlayer= MEM_callocN(sizeof(CDataFileLayer)*(cdf->totlayer+1), "CDataFileLayer");
	memcpy(newlayer, cdf->layer, sizeof(CDataFileLayer)*cdf->totlayer);
	cdf->layer= newlayer;

	cdf->totlayer++;

	/* fill in new layer */
	layer= &cdf->layer[cdf->totlayer-1];
	layer->structbytes= sizeof(CDataFileLayer);
	layer->datatype= CDF_DATA_FLOAT;
	layer->datasize= datasize;
	layer->type= type;
	BLI_strncpy(layer->name, name, CDF_LAYER_NAME_MAX);

	return layer;
}

